// <copyright file="Actions.cs" company="TenneySoftware">
// Copyright (c) 2009 All Right Reserved
// </copyright>
// <author>Robert Tenney</author>
// <email>rob10e@yahoo.com</email>
// <date>April 2009</date>
// <summary>The action class defines all possible actions by characters, and stores which actions a character can perform.</summary>

// License:

// Product: RPGRoguelikeEngine .Net
// Author: Robert N. Tenney
// Copyright: April 2009

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation version 2
// of the License.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

// This license covers all source code, binaries, and other materials related to this project
// unless specifically stated otherwise.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

using TenneySoftware.Engine.Algorithms.Pathfinder;

namespace TenneySoftware.Engine.Characters {
    /// <summary>
    /// The delegate for the Action method.
    /// </summary>
    /// <param name="parameters">Parameters for the action.</param>
    /// <returns>
    /// True if action could be performed.
    /// </returns>
    public delegate bool ActionRedirect(ActionParameters parameters);

    /// <summary>
    /// The action class defines all possible actions by characters, and stores which actions a character can perform.
    /// </summary>
    public class Actions {
        /// <summary>
        /// The action queue with stores the action and the location.
        /// </summary>
        private static Queue<ActionParameters> actionQueue = new Queue<ActionParameters>();

        /// <summary>
        /// Reference to the engine.
        /// </summary>
        private static RPGEngine engine;

        /// <summary>
        /// List of available abilities with a true or false flag to indicate if the specified character can
        /// perform that ability.
        /// </summary>
        private Dictionary<ActionType, bool> abilities;

        /// <summary>
        /// Reference to the character.
        /// </summary>
        private CharacterBase character;

        /// <summary>
        /// Initializes a new instance of the Actions class.
        /// </summary>
        public Actions() {
            this.abilities = new Dictionary<ActionType, bool>();
            this.InitializeAbilities();
        }

        /// <summary>
        /// Gets a value indicating whether there are actions in the queue.
        /// </summary>
        /// <value>
        /// A value indicating whether there are actions in the queue.
        /// </value>
        public static bool ActionsInQueue {
            get {
                return Actions.actionQueue.Count != 0;
            }
        }

        /// <summary>
        /// Gets the action queue.
        /// </summary>
        /// <value>
        /// The action queue.
        /// </value>
        public static Queue<ActionParameters> ActionQueue {
            get {
                return Actions.actionQueue;
            }
        }

        /// <summary>
        /// Gets or sets the RPGEngine reference.
        /// </summary>
        /// <value>
        /// The RPGEngine reference.
        /// </value>
        public static RPGEngine Engine {
            get {
                return Actions.engine;
            }

            set {
                Actions.engine = value;
            }
        }

        /// <summary>
        /// Gets the set list which lists the available abilities.
        /// </summary>
        /// <value>
        /// The set list which lists the available abilities.
        /// </value>
        public Dictionary<ActionType, bool> Abilities {
            get {
                return this.abilities;
            }
        }

        /// <summary>
        /// Gets or sets the Character reference.
        /// </summary>
        /// <value>
        /// The Character reference.
        /// </value>
        public CharacterBase Character {
            get {
                return this.character;
            }

            set {
                this.character = value;
            }
        }

        /// <summary>
        /// Static method that performs the next action in the queue.
        /// </summary>
        public static void PerformNextAction() {
            // Get the Action's Method and location in a KVP.
            ActionParameters redirect = Actions.actionQueue.Dequeue();
            
            engine.IncreaseTurn();
            
            // Invokes the method with the parameter.
            redirect.Redirect(redirect);
        }

        /// <summary>
        /// Moves the character from it's original location to a new location based on the direction given.
        /// </summary>
        /// <param name="destination">Location to move the character to.</param>
        /// <returns>True if successful, false otherwise.</returns>
        public bool Walk(Point destination) {
            this.CheckValidAction(ActionType.Walk);
            ActionParameters temp = new ActionParameters(new ActionRedirect(this.DoWalk));
            temp.Location = destination;
            Actions.actionQueue.Enqueue(temp);
            return true;
        }

        /// <summary>
        /// Moves the character from it's original location to a new location based on the direction given.
        /// </summary>
        /// <param name="direction">Direction to move the character to.</param>
        /// <returns>True if successful, false otherwise.</returns>
        public bool Walk(Direction direction) {
            // Check to see if walk action is something this character can do.
            this.CheckValidAction(ActionType.Walk);

            // Find out where the new location is going to be based on it's relative location.
            Point originalLocation = engine.CharacterList[this.character];
            Point newLocation = Utility.PointForward(originalLocation, direction);

            // Now send the new location to the main walk check and queue.
            return this.Walk(newLocation);
        }

        /// <summary>
        /// Moves the character from it's original point to another point stepping each turn.
        /// </summary>
        /// <param name="location">Location to move to.</param>
        /// <returns>True if a valid path was found.</returns>
        public bool WalkTo(Point location) {
            bool result = false;

            // Check to see if walk action is something this character can do.
            this.CheckValidAction(ActionType.Walk);

            // Check to see if the destination is a valid location for the character to occupy.
            if (engine.CanTileBeOccupied(location)) {
                // Find the path to the destination.
                PathfinderNodeCollection pathSolution = FindPath(engine.CharacterList[this.character], location);
                if (pathSolution != null) {
                    result = true;

                    // If there is a solution then proceed.
                    if (Actions.ActionsInQueue) {
                        // If there are actions in the queue, then clear it in favor of the new path.
                        Actions.actionQueue.Clear();
                    }

                    // Iterate through the solution and add each step to the action queue.
                    for (int iter = pathSolution.Count - 1; iter >= 0; iter--) {
                        PathfinderNode node = pathSolution[iter];
                        this.Walk(new Point(node.X, node.Y));
                    }
                }
            }

            // Solution was found so return true.
            return result;
        }

        /// <summary>
        /// Find a walkable path from one location to another.
        /// </summary>
        /// <param name="start">Starting location to find the path for.</param>
        /// <param name="destination">Destination of the path.</param>
        /// <returns>Solution to the path.</returns>
        private static PathfinderNodeCollection FindPath(Point start, Point destination) {
            IPathfinder pathFinder = new PathfinderFast(engine.ConvertToPathfinder());
            pathFinder.Diagonals = true;
            pathFinder.Formula = HeuristicFormula.Manhattan;
            pathFinder.PunishChangeDirection = false;
            pathFinder.HeavyDiagonals = true;
            pathFinder.Tiebreaker = true;
            pathFinder.SearchLimit = 50000;
            return pathFinder.FindPath(start, destination);
        }

        /// <summary>
        /// Moves the character from it's original location to a new location.
        /// </summary>
        /// <param name="parameter">ActionParameter which holds the destination.</param>
        /// <returns>True if successful, false otherwise.</returns>
        private bool DoWalk(ActionParameters parameter) {
            // Check to see if walk action is something this character can do.
            this.CheckValidAction(ActionType.Walk);

            Point newLocation = parameter.Location;
            
            // All is good, so move the character.
            return engine.SetCharacter(this.character, newLocation);
        }

        /// <summary>
        /// Check to see if the action is something that the character can do, and if the character is on the level.
        /// </summary>
        /// <param name="action">Action to check for.</param>
        private void CheckValidAction(ActionType action) {
            // Check to see if this action is something this character can do.
            if (!this.abilities[action]) {
                throw new ActionNotAllowedException("The character named '" + this.character.Name + "' is not able to " + action.ToString() + ".");
            }

            // Check to see if the referenced character is on the current level.
            if (!engine.CharacterList.ContainsKey(this.character)) {
                throw new CharacterNotOnLevelException(this.character.ToString() + " is not on this level.");
            }
        }

        /// <summary>
        /// Initializes the Abilities list.
        /// </summary>
        private void InitializeAbilities() {
            foreach (string act in ActionType.GetNames(typeof(ActionType))) {
                ActionType action = (ActionType)Enum.Parse(typeof(ActionType), act);
                this.abilities.Add(action, false);
            }
        }
    }
}
